// 字符小数
package big

import (
	"errors"
	"fmt"
	"strings"
)

type 小数 struct { //小数类型
	整数部分, 小数部分 string
	是否为负       bool
}

/*
	小数去点数

原理：

	整数部分与小数部分拼接
*/
func (小数值 小数) 去点数() string { //返回去除小数点的小数
	return 小数值.整数部分 + 小数值.小数部分
}

/*
	小数输出

原理：

	小数整数部分与小数部分之间加点即可表示小数输出形式，再输出（负数加负号）
*/
func (小数值 小数) 输出() { //输出小数
	if 小数值.是否为负 == false {
		fmt.Println(小数值.整数部分 + "." + 小数值.小数部分)
	} else {
		fmt.Println("-", 小数值.整数部分+"."+小数值.小数部分)
	}
}

/*
	小数字符构造

原理：

	调用小数转换，有错误就返回错误，否则返回nil
*/
func (小数值 *小数) 字符构造(小数值1 string) (错误 error) { //构造一个小数
	(*小数值), 错误 = 小数转换(小数值1) //将小数值1转换为小数
	if 错误 != nil {          //有错误
		return 错误
	} else { //无错误
		return nil
	}
}

/*
	小数分段构造

原理：

	整数部分，小数部分，是否为负分别赋值
*/
func (小数值 *小数) 分段构造(整数部分, 小数部分 string, 是否为负 bool) { //构造一个小数
	(*小数值).整数部分 = 整数部分 //整数部分等于小数整数部分
	(*小数值).小数部分 = 小数部分 //分数部分等于小数分数部分
	(*小数值).是否为负 = 是否为负 //是否为负等于小数是否为负
}

/*
	小数比较

原理：

	调用小数比大函数，比较两小数大小
*/
func (小数值 小数) 比较(相比值 小数) string { //比较一小数与另一小数的大小
	大数名 := 小数比大(小数值, "小数值", 相比值, "相比值")
	if 大数名 == "小数值" {
		return "大"
	} else if 大数名 == "相比值" {
		return "小"
	} else {
		return "相等"
	}
}

/*
	加减计算字符小数

原理：

	可以直接被，数相加运算的字符串（小数）,为去除小数点
*/
func 加减计算字符小数(转换值1, 转换值2 小数) (string, string) { //将小数相加（减）两个加数，转换为可以直接被，数相加（减）运算的字符串（小数）
	var (
		转换值1小数位数 = strings.Count(转换值1.小数部分, "") - 1
		转换值2小数位数 = strings.Count(转换值2.小数部分, "") - 1
		转换后值     [2]string
	)
	if 转换值1小数位数 > 转换值2小数位数 {
		转换后值[0] = 转换值1.整数部分 + 转换值1.小数部分
		转换后值[1] = 转换值2.整数部分 + 转换值2.小数部分
	} else {
		转换后值[0] = 转换值1.整数部分 + 转换值1.小数部分
		转换后值[1] = 转换值2.整数部分 + 转换值2.小数部分
	}
	return 转换后值[0], 转换后值[1]
}

/*
	乘计算字符小数

原理：

	可以直接被，数相乘运算的字符串（小数），为去除小数点
*/
func 乘计算字符小数(转换值1, 转换值2 小数) (string, string, uint64) { //将小数相乘两个乘数，转换为可以直接被，数相乘运算的字符串（小数）
	var (
		转换值1小数位数 = uint64(strings.Count(转换值1.小数部分, "") - 1)
		转换值2小数位数 = uint64(strings.Count(转换值2.小数部分, "") - 1)
		转换后值     [2]string
		被转换值位数   uint64
	)
	if 转换值1小数位数 > 转换值2小数位数 {
		被转换值位数 = 转换值1小数位数
		转换后值[0] = 转换值1.整数部分 + 转换值1.小数部分
		转换后值[1] = 转换值2.整数部分 + 转换值2.小数部分
	} else {
		被转换值位数 = 转换值2小数位数
		转换后值[0] = 转换值1.整数部分 + 转换值1.小数部分
		转换后值[1] = 转换值2.整数部分 + 转换值2.小数部分
	}
	被转换值位数 = 转换值1小数位数 + 转换值2小数位数
	return 转换后值[0], 转换后值[1], 被转换值位数
}

/*
	除计算字符小数

原理:

	可以直接被，数相除运算的字符串（小数），为去小数点后，若被除数小数位数>除数小数位数,被除数小数部分补0
*/
func 除计算字符小数(转换值1, 转换值2 小数) (string, string) { //将小数相除两个除数，转换为可以直接被，数相除运算的字符串（小数）
	var (
		转换值1小数位数 = uint64(strings.Count(转换值1.小数部分, "") - 1)
		转换值2小数位数 = uint64(strings.Count(转换值2.小数部分, "") - 1)
		转换后值     [2]string
	)
	if 转换值1小数位数 > 转换值2小数位数 {
		转换后值[0] = 转换值1.整数部分 + 转换值1.小数部分
		转换后值[1] = 转换值2.整数部分 + 转换值2.小数部分
	} else {
		转换后值[0] = 转换值1.整数部分 + 转换值1.小数部分 + strings.Repeat("0", int(转换值2小数位数-转换值1小数位数))
		转换后值[1] = 转换值2.整数部分 + 转换值2.小数部分
	}
	return 转换后值[0], 转换后值[1]
}

/*
	正常字符小数

原理：

	在整数部分与小数部分前加点
*/
func 正常字符小数(转换值 string, 小数位数 uint64) string {
	var (
		整数位数  [2]uint64 //记录整数位数
		正常小数值 string    //保存正常字符小数
	)
	整数位数[0] = uint64(strings.Count(转换值, "") - 1) //记录数位总长
	整数位数[1] = 整数位数[0] - 小数位数                     //记录整数位数
	if 整数位数[0] == 1 {                            //无小数部分
		return 转换值 + "." + "0"
	}
	if 小数位数 == uint64(len(转换值)) { //无整数部分
		return "0" + "." + 转换值
	}
	if 转换值[整数位数[1]:] == "" {
		正常小数值 = 转换值[:整数位数[1]] + "." + "0"
	} else {
		正常小数值 = 转换值[:整数位数[1]] + "." + 转换值[整数位数[1]:] //正确保存正常字符小数
	}
	return 正常小数值
}

/*
	除正常字符小数

原理：

	在整数部分与小数部分前加点
*/
func 除正常字符小数(转换值 string, 整数位数 uint64) string {
	var (
		整数位数1 [2]uint64 //记录整数位数
		正常小数值 string    //保存正常字符小数
	)
	整数位数1[0] = uint64(strings.Count(转换值, "") - 1) //记录数位总长
	整数位数1[1] = 整数位数                               //记录整数位数
	if 整数位数1[0] == 1 {                            //无小数部分
		return 转换值 + "." + "0"
	}
	if 转换值[整数位数1[1]:] == "" {
		正常小数值 = 转换值[:整数位数1[1]] + "." + "0"
	} else {
		正常小数值 = 转换值[:整数位数1[1]] + "." + 转换值[整数位数1[1]:] //正确保存正常字符小数
	}
	return 正常小数值
}

/*
	小数转换

原理：

	小数存在且只存在一个小数点，小数点前为整数部分，小数点后为小数部分，存在多小数点报错
	有负号为负数，存在多负号报错
	小数不存在小数点，全为整数部分，小数部分为0
*/
func 小数转换(小数值 string) (小数, error) { //把字符串转换为小数
	var (
		小数点位 = strings.Index(小数值, ".") //记录小数点的首位置
		小数点数 = strings.Count(小数值, ".") //记录小数点数量
		负号位数 = strings.Index(小数值, "-") //记录负号的首位置
		负号总数 = strings.Count(小数值, "-") //记录负号的数量
		转换值  小数                        //记录转换值
	)
	if 小数点数 > 1 { //小数点数过多
		return 转换值, errors.New("小数点数量过多，小数只有一个小数点！")
	} else if (负号总数 > 1 || 负号位数 >= 1) || (负号总数 > 1 && 负号位数 >= 1) { //负号位置与数量错误
		return 转换值, errors.New("该小数值不能转换，因为不是规范的小数值！")
	} else if 小数点位 == -1 { //不存在小数点
		if 负号位数 == 0 {
			转换值.是否为负 = true
		} else {
			转换值.是否为负 = false
		}
		转换值.小数部分 = "0"
		转换值.整数部分 = 小数值
	} else { //存在小数点
		if 负号位数 == 0 {
			转换值.是否为负 = true
			转换值.小数部分 = 小数值[小数点位+1:]
			转换值.整数部分 = 小数值[1:小数点位]
		} else {
			转换值.是否为负 = false
			转换值.小数部分 = 小数值[小数点位+1:]
			转换值.整数部分 = 小数值[0:小数点位]
		}

	}
	return 转换值, nil
}

/*
	小数比大

原理：

	小数比较大小，可先比整数部分，不相等，大的为大，否则，比小数部分，大的为大，否则，两小数相等
*/
func 小数比大(数1 小数, 数1名称 string, 数2 小数, 数2名称 string) string { //比较两个小数的大小
	var (
		比较结果 string
	)
	比较结果 = 数相比大(数1.整数部分, "数1", 数2.整数部分, "数2") //比较整数部分
	if 比较结果 != "相等" {                         //整数部分不相等
		return 比较结果
	} else { //整数部分相等
		return 数相比大(数1.小数部分, "数1", 数2.小数部分, "数2")
	}
}

/*
	小数相加

原理：

	先转换成计算字符小数，同正相加，同负相加取反，一正一负，正大正减负，负大负减正，结果转换回小数
*/
func 小数相加(加数1, 加数2 小数) 小数 { //将两个小数相加
	var (
		计算字符数   [2]string
		计算字符数位数 [2]uint64
		和       小数
		临时和     [2]string
		小数位     uint64
	)
	计算字符数[0], 计算字符数[1] = 加减计算字符小数(加数1, 加数2) //转计算字符数
	计算字符数位数[0], 计算字符数位数[1] = uint64(strings.Count(计算字符数[0], "")-1), uint64(strings.Count(计算字符数[1], "")-1)
	小数位 = uint64(strings.Count(加数1.小数部分, "") - 1)
	if uint64(strings.Count(加数2.小数部分, "")-1) > 小数位 {
		小数位 = uint64(strings.Count(加数2.小数部分, "") - 1)
	}
	if 加数1.是否为负 == false && 加数2.是否为负 == false { //同为正数，相加
		和.是否为负 = false
		临时和[0] = 数相加(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
		临时和[1] = 正常字符小数(临时和[0], 小数位)
		和, _ = 小数转换(临时和[1])
	} else if 加数1.是否为负 == true && 加数2.是否为负 == true { //同为负数，相加取反
		和.是否为负 = true
		临时和[0] = 数相加(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
		临时和[1] = 正常字符小数(临时和[0], 小数位)
		和, _ = 小数转换(临时和[1])
	} else { //一正一负
		if 大数名 := 小数比大(加数1, "加数1", 加数2, "加数2"); 大数名 == "加数1" { //加数1大
			if 加数1.是否为负 == true { //为负，加数2减加数1
				和.是否为负 = true
				临时和[0], _ = 数相减(计算字符数[1], 计算字符数[0], 计算字符数位数[1], 计算字符数位数[0])
				临时和[1] = 正常字符小数(临时和[0], 小数位)
				和, _ = 小数转换(临时和[1])
			} else { //为正，加数1减加数2
				和.是否为负 = false
				临时和[0], _ = 数相减(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
				临时和[1] = 正常字符小数(临时和[0], 小数位)
				和, _ = 小数转换(临时和[1])
			}
		} else if 大数名 == "加数2" { //加数2大
			if 加数2.是否为负 == true { //为负，加数2减加数1
				和.是否为负 = true
				临时和[0], _ = 数相减(计算字符数[1], 计算字符数[0], 计算字符数位数[1], 计算字符数位数[0])
				临时和[1] = 正常字符小数(临时和[0], 小数位)
				和, _ = 小数转换(临时和[1])
			} else { //为正，加数1减加数2
				和.是否为负 = false
				临时和[0], _ = 数相减(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
				临时和[1] = 正常字符小数(临时和[0], 小数位)
				和, _ = 小数转换(临时和[1])
			}
		} else {
			和 = 小数{"0", "0", false}
		}
	}
	return 和
}

/*
	小数相减

原理：

	先转换成计算字符小数，同正相减，同负相加取反，被减数为负，相加，减数为负，相加取反，结果转换回小数
*/
func 小数相减(被减数, 减数 小数) 小数 { //将两个小数相减
	var (
		计算字符数   [2]string
		计算字符数位数 [2]uint64
		差       小数
		差为正     bool
		临时差     [2]string
		小数位     uint64
	)
	计算字符数[0], 计算字符数[1] = 加减计算字符小数(被减数, 减数) //转计算字符数
	计算字符数位数[0], 计算字符数位数[1] = uint64(strings.Count(计算字符数[0], "")-1), uint64(strings.Count(计算字符数[1], "")-1)
	小数位 = uint64(strings.Count(被减数.小数部分, "") - 1)
	if uint64(strings.Count(减数.小数部分, "")-1) > 小数位 {
		小数位 = uint64(strings.Count(减数.小数部分, "") - 1)
	}
	if 被减数.是否为负 == false && 减数.是否为负 == false { //同为正数，相减
		临时差[0], 差为正 = 数相减(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
		临时差[1] = 正常字符小数(临时差[0], 小数位)
		if 差为正 == true {
			差.是否为负 = false
		} else {
			差.是否为负 = true
		}
		差, _ = 小数转换(临时差[1])
	} else if 被减数.是否为负 == true && 减数.是否为负 == true { //同为负数，相加取反
		临时差[0] = 数相加(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
		临时差[1] = 正常字符小数(临时差[0], 小数位)
		差.是否为负 = false
		差, _ = 小数转换(临时差[1])
	} else { //一正一负
		if 被减数.是否为负 == true { //为负，被减数加减数
			临时差[0] = 数相加(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
			临时差[1] = 正常字符小数(临时差[0], 小数位)
			差.是否为负 = false
			差, _ = 小数转换(临时差[1])
		} else if 减数.是否为负 == true { //为正，被减数加减数，取反
			临时差[0] = 数相加(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1])
			临时差[1] = 正常字符小数(临时差[0], 小数位)
			差.是否为负 = true
			差, _ = 小数转换(临时差[1])
		}
	}
	return 差
}

/*
	小数相乘

原理：

	先转换成计算字符小数，相乘，正正得正，负负得正，正负得负，结果转换回小数
*/
func 小数相乘(乘数1, 乘数2 小数) 小数 { //将两个小数相乘
	var (
		计算字符数   [2]string
		小数位数    uint64
		字符积     [2]string
		计算字符数位数 [2]uint64
		积       小数
	)
	计算字符数[0], 计算字符数[1], 小数位数 = 乘计算字符小数(乘数1, 乘数2) //转计算字符数
	计算字符数位数[0], 计算字符数位数[1] = uint64(strings.Count(计算字符数[0], "")-1), uint64(strings.Count(计算字符数[1], "")-1)
	字符积[0] = 数相乘(计算字符数[0], 计算字符数[1], 计算字符数位数[0], 计算字符数位数[1]) //获取积的字符串形式
	字符积[1] = 正常字符小数(字符积[0], 小数位数)                            //获取积的正常字符小数形式
	积, _ = 小数转换(字符积[1])                                      //获取积的小数形式
	if 乘数1.是否为负 == false && 乘数2.是否为负 == false {              //正正得正
		积.是否为负 = false
	} else if 乘数1.是否为负 == true && 乘数2.是否为负 == true { //负负得正
		积.是否为负 = true
	} else { //正负得负
		积.是否为负 = true
	}
	return 积 //返回积
}

/*
	小数相除

原理：

	先转换成计算字符小数，相除，正正得正，负负得正，正负得负，结果转换回小数
*/
func 小数相除(被除数, 除数 小数) 小数 { //将两个小数相除
	var (
		计算字符数   [2]string
		字符商     [2]string
		计算字符数位数 [2]uint64
		商       小数
		整数位数    uint64
	)
	计算字符数[0], 计算字符数[1] = 除计算字符小数(被除数, 除数) //转计算字符数
	计算字符数位数[0], 计算字符数位数[1] = uint64(strings.Count(计算字符数[0], "")-1), uint64(strings.Count(计算字符数[1], "")-1)
	字符商[0], 整数位数 = 数相除(计算字符数[0], 计算字符数[1], 小数精度) //获取商的字符串形式
	字符商[1] = 除正常字符小数(字符商[0], 整数位数)               //获取商的字符小数形式
	商, _ = 小数转换(字符商[1])                          //获取商的小数形式
	if 被除数.是否为负 == false && 除数.是否为负 == false {   //正正得正
		商.是否为负 = false
	} else if 被除数.是否为负 == true && 除数.是否为负 == true { //负负得负
		商.是否为负 = true
	} else { //正负得负
		商.是否为负 = true
	}
	return 商 //返回商
}
